/*
 * 获取access_token
 * 微信调用全局唯一凭证
 *
 * 特点：
 *  1.唯一的
 *  2.有效性为两小时（提前五分钟请求）
 *  3.接口权限每天2000次
 *
 * 请求地址：
 *  https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
 *
 * 请求方式：
 *  GET
 *
 * 设计思路：
 *  1.首次本地没有，发送请求h获取access_token，保存下来（本地文件）
 *  2.第二次或以后：
 *      -先去本地读取文件，判断是否过期
 *          -过期
 *              -重新请求获取access_token，保存下来覆盖之前的文件（保证文件是唯一的）
 *          -没有过期
 *              -直接使用
 *
 *  整体思路：
 *      读取本地文件
 *      -本地没有文件(getAccessToken/saveAccessToken)
 *          -发送请求h获取access_token，保存下来（本地文件）
 *
 *      -本地有文件(readAccessToken)
 *          -判断它是否过期(isValidAccessToken)
 *              -过期了(getAccessToken/saveAccessToken)
 *                  -重新请求获取access_token，保存下来覆盖之前的文件（保证文件是唯一的）
 *              -没过期
 *                  -直接使用
 *
 */
// 网络请求模块
const rp = require('request-promise-native')
// 引入sha1模块
const sha1 = require('sha1')

// 配置模块
const {appID, appsecret, url} = require('../config')
// 引入menu模块
const menu = require('./menu')
// 引入api模块
const api = require('../utils/api')
// 引入tool模块
const {writeFileAsync, readFileAsync} = require('../utils/tool')


/**
 * 获取access_token
 */
class WeChat {
    constructor() {
    }

    /**
     * 获取 access_token
     */
    getAccessToken() {
        // 定义请求地址
        const url = `${api.accessToken}&appid=${appID}&secret=${appsecret}`
        return new Promise((resolve, reject) => {
            rp({
                method: 'GET',
                url: url,
                json: true,
            }).then(res => {
                // console.log(res)
                // 设置过期时间
                res.expires_in = Date.now() + (res.expires_in - 300) * 1000
                // 将promise对象状态改为成功
                resolve(res)
            }).catch(err => {
                // 将promise对象状态改为失败
                reject('getAccessToken方法出了问题' + err)
            })
        })

    }

    /**
     * 保存 access_token
     * @param accessToken 要保存的凭据
     */
    saveAccessToken(accessToken) {
        return writeFileAsync(accessToken, 'accessToken.txt')
    }

    /**
     * 读取 access_token
     */
    readAccessToken() {
        return readFileAsync('accessToken.txt')
    }

    /**
     * 判断 access_token
     * @param data 数据
     */
    isValidAccessToken(data) {
        // 检测传入的参数是否是有效的
        if (!data && !data.accessToken && !data.expires_in) {
            // 无效的
            return false
        }

        // 检测有效性
        /*if (data.expires_in < Date.now()) {
            // 过期了
            return false
        } else {
            // 没有过期
            return true
        }*/
        return data.expires_in > Date.now()
    }

    /**
     * 用来获取没有过期的 accessToken
     * @returns {Promise<any>} accessToken
     */
    fetchAccessToken() {
        // 优化
        if (this.access_token && this.expires_in && this.isValidAccessToken(this)) {
            // 说明之前保存过access_token，并且是有效的，直接使用
            return Promise.resolve({
                access_token: this.access_token,
                expires_in: this.expires_in
            })
        }

        // 是fetchAccessToken()最终的返回值
        return this.readAccessToken()
            .then(async res => {
                // 本地有文件
                if (this.isValidAccessToken(res)) {
                    //resolve(res)
                    return Promise.resolve(res)
                } else {
                    // 本地没有文件发送请求
                    const res = await this.getAccessToken()
                    // 发送请求获取access_token，保存下来（本地文件）
                    await this.saveAccessToken(res)
                    // 将请求回来的access_token返回出去
                    //resolve(res)
                    return Promise.resolve(res)
                }
            })
            .catch(async err => {
                // 本地没有文件发送请求
                const res = await this.getAccessToken()
                // 发送请求获取access_token，保存下来（本地文件）
                await this.saveAccessToken(res)
                // 将请求回来的access_token返回出去
                //resolve(res)
                return Promise.resolve(res)
            })
            .then(res => {
                // 将access_token挂在到this对象上
                this.access_token = res.access_token
                this.expires_in = res.expires_in

                // 返回res包装了一层promise对象（此对象为成功的状态）
                // 是this.readAccessToken()最终的返回值
                return Promise.resolve(res)
            })
    }

    /**
     * 创建菜单
     * @returns {Promise<any>}
     */
    createMenu(menu) {
        return new Promise(async (resolve, reject) => {
            try {
                // 获取access_token
                const data = await this.fetchAccessToken()
                // 请求地址
                const url = `${api.menu.create}?access_token=${data.access_token}`
                // 发送请求
                const result = rp({method: 'POST', url, json: true, body: menu})
                resolve(result)
            } catch (e) {
                reject('createMenu方法出了问题：' + e)
            }
        })
    }

    /**
     * 删除菜单
     * @returns {Promise<any>}
     */
    deleteMenu() {
        return new Promise(async (resolve, reject) => {
            try {
                // 获取access_token
                const data = await this.fetchAccessToken()
                // 请求地址
                const url = `${api.menu.delete}?access_token=${data.access_token}`
                // 发送请求
                const result = rp({method: 'GET', url, json: true})
                resolve(result)
            } catch (e) {
                reject('deleteMenu方法出了问题：' + e)
            }
        })
    }

    /**
     * 获取 ticket
     */
    getTicket() {
        return new Promise(async (resolve, reject) => {
            // 获取accessToken
            const data = await this.fetchAccessToken()
            // 定义请求地址
            const url = `${api.ticket}&access_token=${data.access_token}`
            rp({
                method: 'GET',
                url: url,
                json: true,
            }).then(res => {
                // 将promise对象状态改为成功
                resolve({ticket: res.ticket, expires_in: Date.now() + (res.expires_in - 300) * 1000})
            }).catch(err => {
                // 将promise对象状态改为失败
                reject('getTicket方法出了问题' + err)
            })
        })

    }

    /**
     * 保存 ticket
     * @param js_ticket 要保存的凭据
     */
    saveTicket(ticket) {
        return writeFileAsync(ticket, 'ticket.txt')
    }

    /**
     * 读取 ticket
     */
    readTicket() {
        return readFileAsync('ticket.txt')
    }

    /**
     * 判断 ticket
     * @param data 数据
     */
    isValidTicket(data) {
        // 检测传入的参数是否是有效的
        if (!data && !data.ticket && !data.expires_in) {
            // 无效的
            return false
        }

        // 检测有效性
        /*if (data.expires_in < Date.now()) {
            // 过期了
            return false
        } else {
            // 没有过期
            return true
        }*/
        return data.expires_in > Date.now()
    }

    /**
     * 用来获取没有过期的 ticket
     * @returns {Promise<any>} ticket
     */
    fetchTicket() {
        // 优化
        if (this.ticket && this.ticket_expires_in && this.isValidTicket(this)) {
            // 说明之前保存过access_token，并且是有效的，直接使用
            return Promise.resolve({
                ticket: this.ticket,
                expires_in: this.expires_in
            })
        }

        // 是fetchTicket()最终的返回值
        return this.readTicket()
            .then(async res => {
                // 本地有文件
                if (this.isValidTicket(res)) {
                    //resolve(res)
                    return Promise.resolve(res)
                } else {
                    // 本地没有文件发送请求
                    const res = await this.getTicket()
                    // 发送请求获取ticket，保存下来（本地文件）
                    await this.saveTicket(res)
                    // 将请求回来的ticket返回出去
                    //resolve(res)
                    return Promise.resolve(res)
                }
            })
            .catch(async err => {
                // 本地没有文件发送请求
                const res = await this.getTicket()
                // 发送请求获取ticket，保存下来（本地文件）
                await this.saveTicket(res)
                // 将请求回来的ticket返回出去
                //resolve(res)
                return Promise.resolve(res)
            })
            .then(res => {
                // 将ticket挂在到this对象上
                this.ticket = res.ticket
                this.ticket_expires_in = res.expires_in

                // 返回res包装了一层promise对象（此对象为成功的状态）
                // 是this.readTicket()最终的返回值
                return Promise.resolve(res)
            })
    }

    /**
     * js-sdk 页面传参
     * @returns {Promise<any>}
     */
    getTicketToPage() {
        return new Promise(async (resolve, reject) => {
            try {
                // 获取票据
                const {ticket} = await this.fetchTicket()
                // 随机字符串
                const noncestr = Math.random().toString().split('.')[1]
                // 时间戳
                const timestamp = Date.now()
                // url

                const arr = [
                    `jsapi_ticket:${ticket}`,
                    `noncestr:${noncestr}`,
                    `timestamp:${timestamp}`,
                    `url:${url}/search`,
                ]
                console.log(arr.sort().join('&'))
                // 字典排序和sha1加密
                const signature = sha1(arr.sort().join('&'))
                //console.log(signature)
                resolve({signature, noncestr, timestamp})
            } catch (err) {
                reject('getTicketToPage方法出了问题：' + err)
            }

        })
    }
}


// 创建菜单
/*(async () => {
    // 模拟测试
    const w = new WeChat()
    // const data = w.fetchAccessToken();
    // console.log(data)
    // 先删除之前的
    let result = await w.deleteMenu()
    console.log(result)
    // // // 再创建
    result = await w.createMenu(menu)
    console.log(result)
    //const data = w.fetchTicket();
    //console.log(data)
})()*/

// 暴露出去
module.exports = WeChat